/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "app_recovery.h"

#include "hilog_wrapper.h"
#include "js_runtime.h"
#include "js_runtime_utils.h"

#include "napi/native_api.h"
#include "napi/native_common.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "parcel.h"
#include "want_params.h"

namespace OHOS {
namespace AbilityRuntime {
namespace {
static std::string GetSaveAppCachePath(int32_t savedStateId)
{
    auto context = AbilityRuntime::Context::GetApplicationContext();
    if (context == nullptr) {
        return "";
    }

    std::string fileDir = context->GetFileDir();
    if (fileDir.empty()) {
        return "";
    }

    std::string filePath = "/proc/" + std::to_string(getpid()) + "/root/" + fileDir;
    std::string fileName = std::to_string(savedStateId) + ".state";
    return filePath + "/" + fileName;
}
}
AppRecovery::AppRecovery() : isEnable_(false), restartFlag_(RestartFlag::ALWAYS_RESTART),
    saveFlag_(SaveFlag::SAVE_WHEN_ERROR), saveMode_(SaveMode::SAVE_WITH_FILE)
{
    isEnable_ = true;
}

AppRecovery::~AppRecovery()
{
}

AppRecovery& AppRecovery::GetInstance()
{
    static AppRecovery instance;
    return instance;
}

bool AppRecovery::InitApplicationInfo(const std::shared_ptr<Ability> &ability, const std::shared_ptr<AbilityInfo> &abilityInfo)
{
    if (!isEnable_) {
        return false;
    }

    HILOG_INFO("AppRecovery InitApplicationInfo.");
    ability_ = ability;
    abilityInfo_ = abilityInfo;
    std::lock_guard<std::mutex> lock(lock_);
    if ((mainHandler_ == nullptr) && (EventRunner::GetMainEventRunner() != nullptr)) {
        mainHandler_ = std::make_shared<EventHandler>(EventRunner::GetMainEventRunner());
    }
    return true;
}

// should be called from main thread?
bool AppRecovery::SaveAbilityState(int32_t savedStateId)
{
    if (!isEnable_) {
        return false;
    }

    bool needPersistState = false;
    HILOG_INFO("%{public}s called begin", __func__);
    std::shared_ptr<Ability> ability = nullptr;
    ability = ability_.lock();
    if (ability == nullptr) {
        HILOG_ERROR("ability is nullptr");
        return false;
    }

    Want want;
    WantParams wantParams = want.GetParams();
    HILOG_INFO("OnSaveState begin");
    int32_t status = ability->OnSaveState(SaveStateReason::APP_RECOVERY, wantParams);
    HILOG_INFO("OnContinue end, status: %{public}d", status);
    if (status != 0) {
        HILOG_ERROR("Failed to save user params.");
    } else {
        needPersistState = true;
    }

#ifdef SUPPORT_GRAPHICS
    std::string pageStack = ability->GetContentInfo();
    if (!pageStack.empty()) {
        wantParams.SetParam(PAGE_STACK_PROPERTY_NAME, String::Box(pageStack));
        HILOG_INFO("ability pageStack: %{public}s", pageStack.c_str());
        needPersistState = true;
    } else {
        HILOG_ERROR("Failed to get page stack.");
    }
#endif
    HILOG_INFO("%{public}s called end", __func__);
    if (needPersistState) {
        SerializeDataToFile(savedStateId, param);
    }
    return true;
}

bool AppRecovery::SerializeDataToFile(int32_t savedStateId, WantParams& params)
{
    std::string file = GetSaveAppCachePath(savedStateId);
    if (file.empty()) {
        HILOG_INFO("%{public}s failed to persisted file path.", __func__);
        return false;
    }
    // todo use memFd ? sql will not be support?
    Parcel parcel;
    if (params.Marshalling(parcel)) {
        HILOG_INFO("%{public}s failed to Marshalling want param.", __func__);
        return false;
    }

    int fd = open(file.c_str(), O_RDWR | O_CREAT, (mode_t)0600);
    if (fd <= 0) {
        return false;
    }

    size_t sz = parcel.GetDataSize();
    uintptr_t buf = parcel.GetData();
    if (sz == 0 || buf == nullptr) {
        HILOG_INFO("%{public}s failed to get parcel data.", __func__);
        closed(fd);
        return false;
    }

    size_t nwrite = write(fd, (uint8_t*)buf, sz);
    if (nwrite != sz) {
        HILOG_INFO("%{public}s failed to persist parcel data %{public}d.", __func__, errno);
    }
    closed(fd);
    return true;
}

bool AppRecovery::ReadSerializeDataFromFile(int32_t savedStateId, WantParams& params)
{
    std::string file = GetSaveAppCachePath(savedStateId);
    if (file.empty()) {
        HILOG_INFO("%{public}s failed to persisted file path.", __func__);
        return false;
    }

    // Get file descriptor and file length
    int32_t fd = open(filename, O_RDONLY);
    if (fd <= 0) {
        HILOG_INFO("fopen error");
        return false;
    }

    struct stat statbuf;
    if (fstat(fd, &statbuf) < 0) {
        HILOG_INFO("fstat error");
        close(fd);
        return false;
    }

    auto mapFile = (uint8_t*) mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (mapFile == MAP_FAILED) {
        HILOG_INFO("mmap error");
        close(fd);
        return false;
    }

    Parcel parcel;
    if (!parcel.ParseFrom(reinterpret_cast<uintptr_t>(mapFile), statbuf.st_size)) {
        HILOG_INFO("failed to parse parcel.");
        munmap(mapFile, statbuf.st_size);
        close(fd);
        return false;
    }

    auto parsedParam = WantParams::Unmarshalling(parcel);
    if (parsedParam != nullptr) {
        params = *parsedParam;
        delete parsedParam;
    } else {
        HILOG_INFO("failed to Unmarshalling parcel.");
        munmap(mapFile, statbuf.st_size);
        close(fd);
        return false;
    }

    munmap(mapFile, statbuf.st_size);
    close(fd);
    return true;
}

void AppRecovery::EnableAppRecovery(RestartFlag restartFlag, SaveFlag saveFlag, SaveMode saveMode)
{
    isEnable_ = true;
    restartFlag_ = restartFlag;
    saveFlag_ = saveFlag;
    saveMode_ = saveMode;
}

bool AppRecovery::IsEnabled() const
{
    return isEnable_;
}

RestartFlag AppRecovery::GetRestartFlag() const
{
    return restartFlag_;
}

SaveFlag AppRecovery::GetSaveFlag() const
{
    return saveFlag_;
}

SaveMode AppRecovery::GetSaveMode() const
{
    return saveMode_;
}
}  // namespace AbilityRuntime
}  // namespace OHOS
